#ifndef SPONGE_LIBSPONGE_ROUTER_HH
#define SPONGE_LIBSPONGE_ROUTER_HH

#include "network_interface.hh"

#include <optional>
#include <queue>

//! \brief A wrapper for NetworkInterface that makes the host-side
//! interface asynchronous: instead of returning received datagrams
//! immediately (from the `recv_frame` method), it stores them for
//! later retrieval. Otherwise, behaves identically to the underlying
//! implementation of NetworkInterface.
class AsyncNetworkInterface : public NetworkInterface {
  std::queue<InternetDatagram> _datagrams_out{};

 public:
  using NetworkInterface::NetworkInterface;

  //! Construct from a NetworkInterface
  AsyncNetworkInterface(NetworkInterface &&interface) : NetworkInterface(interface) {}

  //! \brief Receives and Ethernet frame and responds appropriately.

  //! - If type is IPv4, pushes to the `datagrams_out` queue for later retrieval by the owner.
  //! - If type is ARP request, learn a mapping from the "sender" fields, and send an ARP reply.
  //! - If type is ARP reply, learn a mapping from the "target" fields.
  //!
  //! \param[in] frame the incoming Ethernet frame
  void recv_frame(const EthernetFrame &frame) {
    auto optional_dgram = NetworkInterface::recv_frame(frame);
    if (optional_dgram.has_value()) {
      _datagrams_out.push(std::move(optional_dgram.value()));
    }
  };

  //! Access queue of Internet datagrams that have been received
  std::queue<InternetDatagram> &datagrams_out() { return _datagrams_out; }
};

//! \brief A router that has multiple network interfaces and
//! performs longest-prefix-match routing between them.
class Router {
  //! The router's collection of network interfaces
  std::vector<AsyncNetworkInterface> _interfaces{};

  //! Send a single datagram from the appropriate outbound interface to the next hop,
  //! as specified by the route with the longest prefix_length that matches the
  //! datagram's destination address.
  void route_one_datagram(InternetDatagram &dgram);

  struct RouterTableEntry {
    const uint32_t route_prefix;
    const uint8_t prefix_length;
    const std::optional<Address> next_hop;
    const size_t interface_idx;
  };
  std::vector<RouterTableEntry> _route_table{};

 public:
  //! Add an interface to the router
  //! \param[in] interface an already-constructed network interface
  //! \returns The index of the interface after it has been added to the router
  size_t add_interface(AsyncNetworkInterface &&interface) {
    _interfaces.push_back(std::move(interface));
    return _interfaces.size() - 1;
  }

  //! Access an interface by index
  AsyncNetworkInterface &interface(const size_t N) { return _interfaces.at(N); }

  //! Add a route (a forwarding rule)
  void add_route(const uint32_t route_prefix,
                 const uint8_t prefix_length,
                 const std::optional<Address> next_hop,
                 const size_t interface_num);

  //! Route packets between the interfaces
  void route();
};

#endif  // SPONGE_LIBSPONGE_ROUTER_HH
